﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using LiteDB;
using Newtonsoft.Json;
using NLog;
using SmartQuant;

namespace QuantBox
{
    public class PositionStore
    {
        private readonly Logger _logger;
        private readonly Framework _framework;
        private readonly ILiteCollection<PositionRecord>? _table;
        private readonly IdArray<PositionManager> _positionManagers = new(100);

        public PositionStore(Framework framework, ILiteCollection<PositionRecord>? table = null, Logger? logger = null)
        {
            _framework = framework;
            _table = table;
            _logger = logger ?? LogManager.GetLogger("positions");
        }

        public void LoadPosition()
        {
            if (_table == null)
                return;

            var dict = _framework.StrategyManager.GetStrategyDict();
            foreach (var position in _table.FindAll())
            {
                if (!dict.TryGetValue(position.StrategyName, out var strategy))
                {
                    if (!int.TryParse(position.StrategyName, out var id))
                    {
                        continue;
                    }
                    strategy = _framework.StrategyManager.FindStrategy(id);
                }

                if (strategy == null)
                {
                    _logger.Warn($"加载持仓失败, {position.StrategyName}, {position.Symbol}, {position.Direction}");
                    continue;
                }

                var instrument = _framework.InstrumentManager.Get(position.Symbol);
                if (instrument == null)
                {
                    _logger.Warn($"加载持仓失败, {position.StrategyName}, {position.Symbol}, {position.Direction}");
                    continue;
                }

                var manager = _positionManagers[strategy.Id] ?? new PositionManager(position.StrategyName);
                manager.SetPosition(instrument, position);
                _positionManagers[strategy.Id] = manager;
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public DualPosition? GetPosition(int strategyId, int instrumentId, bool createNew = true)
        {
            return GetPositionManager(strategyId, createNew)?.GetPosition(_framework.InstrumentManager.GetById(instrumentId), createNew);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public DualPosition? GetPosition(int strategyId, Instrument instrument, bool createNew = true)
        {
            return GetPositionManager(strategyId, createNew)?.GetPosition(instrument, createNew);
        }

        public PositionManager? GetPositionManager(int strategyId, bool createNew = true)
        {
            var manager = _positionManagers[strategyId];
            if (manager != null)
            {
                return manager;
            }

            if (createNew)
            {
                var strategy = _framework.StrategyManager.FindStrategy(strategyId);
                if (strategy == null)
                {
                    //OrderAgent Strategy
                    manager = new PositionManager(strategyId.ToString());
                    _positionManagers[strategyId] = manager;
                    return manager;
                }
                return GetPositionManager(strategy);
            }

            return null;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public DualPosition GetPosition(Order order)
        {
            return GetPositionManager(order.strategyId)!.GetPosition(order.Instrument)!;
        }

        public PositionManager GetPositionManager(Strategy strategy)
        {
            var manager = _positionManagers[strategy.Id];
            if (manager != null)
            {
                return manager;
            }

            if (strategy is InstrumentStrategy {IsInstance: true} iss)
            {
                manager = GetPositionManager(iss.Parent.Id)!;
            }
            else
            {
                manager = new PositionManager(strategy.Name);
            }
            _positionManagers[strategy.Id] = manager;
            return manager;
        }

        public void Load(IList<ExecutionCommand> cmdList, IdArray<List<ExecutionReport>> recordMap)
        {
            var marketCloseTime = GetLastMarketCloseTime();
            var tradingDay = DateTime.MinValue;

            foreach (var cmd in cmdList)
            {
                if (cmd.Type != ExecutionCommandType.Send)
                {
                    continue;
                }
                var order = new Order(cmd)
                {
                    Instrument = _framework.InstrumentManager.GetById(cmd.InstrumentId)
                };
                if (tradingDay == DateTime.MinValue)
                {
                    tradingDay = order.TransactTime.Date;
                }
                var manager = GetPositionManager(order.strategyId)!;
                var position = GetPosition(order);
                if (tradingDay < order.TransactTime.Date)
                {
                    manager.ChangeTradingDay();
                }

                position.FrozenPosition(order);
                var reports = recordMap[order.Id];
                if (reports != null)
                {
                    foreach (var report in reports)
                    {
                        report.Order = order;
                        position.ProcessExecutionReport(report);
                    }
                }
            }

            if (cmdList.Count > 0 && cmdList.Last().TransactTime < marketCloseTime)
            {
                for (var i = 0; i < _positionManagers.Size; i++)
                {
                    _positionManagers[i]?.ChangeTradingDay();
                }
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public PositionRecord UpdatePosition(Order order, bool saveToDatabase = true)
        {
            var position = GetPosition(order.StrategyId, order.Instrument.Id)!;
            var record = position.FrozenPosition(order);
            if (saveToDatabase)
            {
                _table?.Upsert(record);
            }
            return record;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public PositionRecord? UpdatePosition(ExecutionReport report, bool saveToDatabase = true)
        {
            var position = GetPosition(report.StrategyId, report.InstrumentId, false);
            var record = position?.ProcessExecutionReport(report);
            if (record != null)
            {
                if (saveToDatabase)
                {
                    _table?.Upsert(record);
                }
            }
            else
            {
                switch (report.ExecType)
                {
                    case ExecType.ExecRejected:
                    case ExecType.ExecCancelled:
                    case ExecType.ExecTrade:
                        _logger.Debug(JsonConvert.SerializeObject(report, Formatting.Indented));
                        _logger.Error("订单回报未定位到持仓");
                        break;
                }
            }
            return record;
        }

        public static TimeSpan MarketCloseTime { get; set; } = new(15, 30, 0);
        private static DateTime GetLastMarketCloseTime()
        {
            if (DateTime.Now.TimeOfDay > MarketCloseTime)
            {
                return DateTime.Today.Add(MarketCloseTime);
            }

            var date = DateTime.Today.AddDays(-1);
            while (date.DayOfWeek != DayOfWeek.Saturday)
            {
                date = date.AddDays(-1);
            }

            return date.Add(MarketCloseTime);
        }

        public void ChangeTradingDay()
        {
            _logger.Debug($"{nameof(ChangeTradingDay)}");
            for (var i = 0; i < _positionManagers.Size; i++)
            {
                var manager = _positionManagers[i];
                if (manager == null)
                {
                    continue;
                }
                manager.ChangeTradingDay();
                foreach (var position in manager.GetAllPositions())
                {
                    _table?.Upsert(position.Long);
                    _logger.Debug($"{nameof(ChangeTradingDay)},{position.Long}");
                    _table?.Upsert(position.Short);
                    _logger.Debug($"{nameof(ChangeTradingDay)},{position.Short}");
                }
            }
        }
    }
}